package com.bestcem.xm.ticket.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.lang.ObjectId;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONValidator;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.enums.ReqErrorCodeEnum;
import com.bestcem.xm.common.core.enums.ResourceErrorCodeEnum;
import com.bestcem.xm.common.core.oss.enums.OssPathEnum;
import com.bestcem.xm.common.core.uitls.DateUtil;
import com.bestcem.xm.common.core.uitls.ExcelExportUtil;
import com.bestcem.xm.componet.filestorage.service.StorageService;
import com.bestcem.xm.ticket.constant.Constants;
import com.bestcem.xm.ticket.constant.ExcelConstant;
import com.bestcem.xm.ticket.controller.convert.TicketInfoVOConvert;
import com.bestcem.xm.ticket.controller.convert.TicketVOConvert;
import com.bestcem.xm.ticket.controller.param.ExportTicketParam;
import com.bestcem.xm.ticket.controller.vo.SimpleIdVO;
import com.bestcem.xm.ticket.controller.vo.ticket.TicketInfoVO;
import com.bestcem.xm.ticket.controller.vo.ticket.TicketPageVO;
import com.bestcem.xm.ticket.dao.TicketConfigDao;
import com.bestcem.xm.ticket.dao.TicketDao;
import com.bestcem.xm.ticket.dao.TicketRecordDao;
import com.bestcem.xm.ticket.dao.dto.ExportTicketDTO;
import com.bestcem.xm.ticket.dao.dto.TicketInfo;
import com.bestcem.xm.ticket.entity.mongo.*;
import com.bestcem.xm.ticket.enums.*;
import com.bestcem.xm.ticket.grpc.client.TicketBaseDeliverGrpcService;
import com.bestcem.xm.ticket.grpc.client.TicketBaseQdesGrpcService;
import com.bestcem.xm.ticket.grpc.client.TicketBaseUserGrpcService;
import com.bestcem.xm.ticket.grpc.client.TicketSurveyGrpcService;
import com.bestcem.xm.ticket.grpc.client.dto.deliver.DeliverAgainRecordDTO;
import com.bestcem.xm.ticket.grpc.client.dto.qdes.QdesDTO;
import com.bestcem.xm.ticket.grpc.client.dto.survey.ContactNodeDTO;
import com.bestcem.xm.ticket.grpc.client.dto.user.UserDTO;
import com.bestcem.xm.ticket.mq.send.biz.TicketMessageSendService;
import com.bestcem.xm.ticket.service.DeliverAgainService;
import com.bestcem.xm.ticket.service.TicketRecordService;
import com.bestcem.xm.ticket.service.TicketService;
import com.bestcem.xm.ticket.service.convert.TicketInfoConvert;
import com.bestcem.xm.ticket.service.dto.DatetimeFilter;
import com.bestcem.xm.ticket.service.dto.FileDTO;
import com.bestcem.xm.ticket.service.dto.PageDTO;
import com.bestcem.xm.ticket.service.dto.RegionFilter;
import com.bestcem.xm.ticket.service.dto.dashboard.DashboardDTO;
import com.bestcem.xm.ticket.service.dto.dashboard.DateTypeCountDTO;
import com.bestcem.xm.ticket.service.dto.dashboard.StatusCountDTO;
import com.bestcem.xm.ticket.service.dto.mq.MemberFootPrint;
import com.bestcem.xm.ticket.service.dto.mq.TicketStatusUpdateDto;
import com.bestcem.xm.ticket.service.manager.role.TicketFollower;
import com.bestcem.xm.ticket.task.TicketTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.io.File;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author liheng
 * @version v1.0
 * @date 2021/8/18 14:56
 */
@Slf4j
@Service
public class TicketServiceImpl implements TicketService {

    @Autowired
    private TicketRecordService ticketRecordService;
    @Autowired
    private DeliverAgainService deliverAgainService;
    //@Resource
    //private OssService ossService;

    @Resource
    private StorageService storageService;

    @Autowired
    private TicketSurveyGrpcService ticketSurveyGrpcService;
    @Autowired
    private TicketBaseUserGrpcService ticketBaseUserGrpcService;
    @Autowired
    private TicketBaseDeliverGrpcService ticketBaseDeliverGrpcService;
    @Autowired
    private TicketBaseQdesGrpcService ticketBaseQdesGrpcService;

    @Autowired
    private TicketTask ticketTask;
    //@Autowired
    //private TemplateDao templateDao;
    @Autowired
    private TicketDao ticketDao;
    @Autowired
    private TicketRecordDao ticketRecordDao;
    @Autowired
    private TicketConfigDao ticketConfigDao;

    //@Autowired
    //private TicketUtil ticketUtil;
    //@Autowired
    //private TicketMqProductUtil ticketMqProductUtil;

    @Autowired
    private TicketMessageSendService ticketMessageSendService;
    //@Autowired
    //private TicketFsmManager ticketFsmManager;
//    @Autowired
//    private MqUtil mqUtil;

    @Override
    public Boolean checkSurveyHasTicket(String surveyId) {
        long count = ticketDao.countBySurveyId(surveyId);
        return count > 0L;
    }

    /**
     * 根据id查询下一个Ticket
     *
     * @param id     工单id
     * @param orgId  公司id
     * @param userId 当前用户id
     * @return Ticket 信息
     * @author zq.ding <zq.ding@idiaoyan.com>
     * @date 2021/8/11 15:36
     */
    @Override
    public ServiceResult<SimpleIdVO> nextTicket(String id, String orgId, String userId) {
        Ticket ticket = ticketDao.findById(id);
        if (Objects.isNull(ticket)) {
            return ServiceResult.failWithCode(
                    ResourceErrorCodeEnum.NOT_FOUND, "ticket_id: " + id + " status is following not found");
        }
        if (StrUtil.isEmpty(orgId) || StrUtil.isEmpty(userId)) {
            return ServiceResult.failWithCode(ReqErrorCodeEnum.PARAM_LOST, "公司id或用户id为空");
        }
        Ticket nextTicket = ticketDao.findNext(id, orgId, userId);
        // nextTicket为空或id属性为空时,返回数据为空。
        if (Objects.isNull(nextTicket) || StrUtil.isEmpty(nextTicket.getId())) {
            return ServiceResult.success(new SimpleIdVO(Constants.BLANK));
        }

        return ServiceResult.success(new SimpleIdVO(nextTicket.getId()));
    }

    /**
     * 获取单个工单信息 ( 2.3版 )
     *
     * @param id 工单id
     * @return 返回前端需要的工单信息
     * @author liheng
     * @author XiaoYunSong <ys.xiao@idiaoyan.com>
     * @date 2021/9/10 0:50
     */
    @Override
    public ServiceResult<TicketInfoVO> getTicketInfo(String id, String userId) {
        Ticket deletedTicket = ticketDao.findDeletedById(id);
        if (Objects.nonNull(deletedTicket)) {
            return ServiceResult.failWithCode(ResourceErrorCodeEnum.TICKET_DELETED, "工单已被删除");
        }

        List<TicketInfo> ticketInfos = ticketDao.lookUpTicketRecord(id);
        if (CollectionUtils.isEmpty(ticketInfos)) {
            log.error("[Ticket] ticket id={} not found", id);
            return ServiceResult.failWithCode(ResourceErrorCodeEnum.NOT_FOUND, "工单不存在");
        }

        TicketInfo ticketInfo = ticketInfos.get(0);
        // 由于TicketInfoNewConvert.entityToTicketInfoNewVO里面是传唤逻辑，所以不想再里面注入bean
        // 在此改变ticketInfo中的字段。
        // 转化答卷答案数据
        try {
            if (Objects.isNull(ticketInfo.getOperationType()) || Integer.valueOf(1).equals(ticketInfo.getOperationType())) {
                // 新增工单详情字段
                List<Ticket.TemplateDetailField> templateDetailFields = ticketInfo.getTemplateDetailFields();
                if (!CollectionUtils.isEmpty(templateDetailFields)) {
                    if (log.isInfoEnabled()) {
                        log.info("[Ticket] 获取到的data {}", JSONObject.toJSONString(templateDetailFields));
                    }
                    Long seq = ticketInfo.getResId();
                    String projectId = ticketInfo.getSurveyId();
                    // 获取问卷数据
                    String data = ticketSurveyGrpcService.getAllRespondentData(projectId, userId, seq);
                    if (!ObjectUtils.isEmpty(data)) {
                        JSONValidator from = JSONValidator.from(data);
                        if (from.validate()) {
                            if (log.isInfoEnabled()) {
                                log.info("[Ticket] 获取到的data {}", data);
                            }
                            JSONObject jsonData = JSONObject.parseObject(data);
                            for (Ticket.TemplateDetailField field : templateDetailFields) {
                                // 问卷数据答案返回前端
                                if (jsonData.containsKey(field.getSourceId())) {
                                    String sourceId = field.getSourceId();
                                    JSONObject jsonObject = jsonData.getJSONObject(sourceId);
                                    Ticket.TemplateDetailField.SurveyAnswer answer = new Ticket.TemplateDetailField.SurveyAnswer();
                                    if (Objects.nonNull(jsonObject)) {
                                        Object jsonAnswer = jsonObject.get("answer");
                                        if (Objects.nonNull(jsonAnswer)) {
                                            String jsonStr = jsonAnswer.toString();
                                            try {
                                                answer.setAnswer(JSONObject.parseObject(jsonStr, JSONObject.class));
                                            } catch (Exception e) {
                                                answer.setAnswer(jsonStr);
                                            }
                                        }
                                        answer.setQtype(Objects.nonNull(jsonObject.get("qtype")) ? jsonObject.get("qtype").toString() : null);
                                        answer.setCId(Objects.nonNull(jsonObject.get("cid")) ? jsonObject.get("cid").toString() : null);
                                        answer.setCustomQType(Objects.nonNull(jsonObject.get("custom_qtype")) ? jsonObject.get("custom_qtype").toString() : null);
                                        field.setValue(answer);
                                    }
                                    if (log.isInfoEnabled()) {
                                        log.info("[Ticket] 获取到的answer {}", JSONObject.toJSONString(field));
                                    }
                                }
                            }
                        }
                    }
                }
                ticketInfo.setTemplateDetailFields(templateDetailFields);

                // rpc获取contact值
                ContactNodeDTO contactData = ticketSurveyGrpcService.getContactBySeq(ticketInfo.getResId(), ticketInfo.getSurveyId());
                if (Objects.nonNull(contactData)) {
                    if (log.isInfoEnabled()) {
                        log.info("[ticket]:获取到的contact {}:", JSONObject.toJSONString(contactData));
                    }
                    TicketInfo.Contact contact = new TicketInfo.Contact();
                    contact.setEmail(contactData.getPhone());
                    contact.setPhone(contactData.getEmail());
                    ticketInfo.setContact(contact);
                }
            }
        } catch (Exception e) {
            log.error("[Ticket] 解析答卷数据失败", e);
        }

        TicketInfoVO ticketInfoVO = TicketInfoVOConvert.entityToTicketInfoVO(ticketInfo);
        // 获取工单对应的问卷项目
        QdesDTO qdesDTO = ticketBaseQdesGrpcService.getQdesProject(ticketInfo.getSurveyId());
        if (Objects.nonNull(qdesDTO)) {
            // 工单对应的问卷项目名称更新为最新的名称
            ticketInfoVO.setSurveyTitle(qdesDTO.getTitle());
        }

        ArrayList<TicketInfoVO.AlertRuleVO> alertRules = new ArrayList<>();
        if (ObjectId.isValid(ticketInfo.getTicketConfigId())) {
            TicketConfig ticketConfig = ticketConfigDao.findById(ticketInfo.getTicketConfigId());
            if (Objects.nonNull(ticketConfig) && Objects.nonNull(ticketConfig.getTicketRuleId())) {
                // 2.2 P2 ticket_config表 整体作为工单生成规则，ticket_config表 的 title字段即为 生成规则标题
                TicketInfoVO.AlertRuleVO alertRule =
                        new TicketInfoVO.AlertRuleVO(ticketConfig.getId(), ticketConfig.getTitle());
                alertRules.add(alertRule);
            }
        }
        ticketInfoVO.setAlertRules(alertRules);

        List<UserDTO> userDTOS = getCurrentTicketUsers(ticketInfo);
        String alertRecordId = ticketInfoVO.getAlertRecordId();
        ticketInfoVO.setAlertRecordId(Optional.ofNullable(alertRecordId).orElse(""));
        List<UserDTO> userDTOList = Optional.ofNullable(userDTOS).orElse(new ArrayList<>());

        ticketInfoVO.setCurrentUsers(userDTOList.stream().map(UserDTO::getName).collect(Collectors.toList()));
        ticketInfoVO.setCurrentUsersIds(userDTOList.stream().map(UserDTO::getId).collect(Collectors.toList()));

        return ServiceResult.success(ticketInfoVO);
    }

    @Override
    public void updateTicketStatusAndDeliver(TicketStatusUpdateDto ticketStatusUpdateDto) {
        if (log.isInfoEnabled()) {
            log.info("[ticket] alert_rule_triggered data={}", ticketStatusUpdateDto);
        }
        // 1. 校验必传参数
        if (!validTicketStatusUpdateDto(ticketStatusUpdateDto)) {
            return;
        }
        String ruleId = ticketStatusUpdateDto.getRuleIdList().get(0);
        // 2. 获取二次投放记录
        DeliverAgainRecordDTO deliverAgainRecordDTO = ticketBaseDeliverGrpcService.getAgainDeliverRecord(ticketStatusUpdateDto.getRespondentData().getAgainDeliverRecordId());
        if (Validator.isNull(deliverAgainRecordDTO)) {
            log.error("[ticket] 二次投放更改工单状态 没有找到againDeliverRecordId={}对应的二次投放记录", ticketStatusUpdateDto.getRespondentData().getAgainDeliverRecordId());
            return;
        }
        // 3. 根据投放记录获取工单信息
        Ticket ticket = ticketDao.findById(deliverAgainRecordDTO.getTicketId());
        if (Validator.isNull(ticket)) {
            log.error("[ticket] 二次投放更改工单状态 没有找到id={}对应的工单", deliverAgainRecordDTO.getTicketId());
            return;
        }
        if (TicketStatusEnum.FINISH.getIndex().equals(ticket.getStatus()) || TicketStatusEnum.FAILED.getIndex().equals(ticket.getStatus())) {
            log.error("[ticket] 二次投放更改工单状态 工单状态 = {}无需改变", ticket.getStatus());
            return;
        }
        // 4. 根据工单配置id查找工单配置
        TicketConfig ticketConfig = ticketConfigDao.findById(ticket.getTicketConfigId());
        if (Validator.isNull(ticketConfig)) {
            log.error("[ticket] 二次投放更改工单状态 没有找到工单id={}对应的工单配置", deliverAgainRecordDTO.getTicketId());
            return;
        }
        // 5. 得到工单配置中的状态规则。
        List<JSONObject> statusRuleList = ticketConfig.getStatusRuleList();
        if (CollUtil.isEmpty(statusRuleList)) {
            return;
        }
        if (log.isInfoEnabled()) {
            log.info("[ticket] 二次投放更改工单状态 工单id={}, 工单配置id={}", ticket.getId(), ticketConfig.getId());
        }
        // 6. 循环判断是否应该创建二次投放
        for (JSONObject statusRule : statusRuleList) {
            // 6.1 获取到规则对应的操作，这里默认值为UNSPECIFIED_TICKET_OPERATE，
            String operate = statusRule.getOrDefault(ruleId, Constants.UNSPECIFIED_TICKET_OPERATE).toString();
            handleTicket(operate, ticket);
        }
    }

    /**
     * 1.创建工单记录
     * 2.发送工单记录mq
     * 3.根据工单id检查并进行二次投放
     * 4.更新工单状态
     *
     * @param ticket  信息工单
     * @param operate 操作，1.工单处理成功 2.工单处理失败。 目前可能会只有这两种操作，
     * @author jy.zhao
     * @date 2021/8/26 14:22
     **/
    @Override
    public void handleTicket(String operate, Ticket ticket) {
        // 无意义的工单操作
        if (Constants.UNSPECIFIED_TICKET_OPERATE.equals(operate)) {
            return;
        }
        if (operate.equals(TicketHandleStatusEnum.SUCCESS.getCode())) {
            // 6.2 设置工单状态
            ticket.setStatus(TicketStatusEnum.FINISH.getIndex());
            // 6.3 创建工单记录
            TicketRecord ticketRecord = ticketRecordService.createTicketRecord(ticket, TicketRecordEventEnum.FINISH);
            // 6.4 发送工单记录mq
            ticketMessageSendService.publishTicketRecordCreateMq(ticket, ticketRecord);
            // 6.5 根据工单id检查并进行二次投放
            deliverAgainService.validTicketAndDeliverAgain(ticket, 0, null);
        } else if (operate.equals(TicketHandleStatusEnum.FAILED.getCode())) {
            ticket.setStatus(TicketStatusEnum.FAILED.getIndex());
            TicketRecord ticketRecord = ticketRecordService.createTicketRecord(ticket, TicketRecordEventEnum.FAILED);
            ticketMessageSendService.publishTicketRecordCreateMq(ticket, ticketRecord);
            // 根据工单id检查并进行二次投放
            deliverAgainService.validTicketAndDeliverAgain(ticket, 0, null);
        }
        // 这里暂定如此。和赵明确定目前可能有两种状态。
        // 6.6 更新工单状态
        TicketStatusEnum statusEnum = TicketStatusEnum.getByIndex(ticket.getStatus());
        if (Validator.isNull(statusEnum)) {
            return;
        }
        ticketDao.updateStatus(ticket.getId(), statusEnum);
        // 工单状态改变同步到足迹点
        ticketMessageSendService.publishMemberFootSaveMqRefactor(ticket);
        if (log.isInfoEnabled()) {
            log.info("[ticket] 工单状态改变={}同步到足迹点", ticket.getId());
        }
        // 6.7 发送工单状态变化的mq
        ticketMessageSendService.publishTicketChangeMq(ticket);
    }

    /**
     * 校验TicketStatusUpdateDto参数
     *
     * @param ticketStatusUpdateDto 参数
     * @return boolean 返回
     * @author jy.zhao
     * @date 2021/8/26 10:59
     **/
    private boolean validTicketStatusUpdateDto(TicketStatusUpdateDto ticketStatusUpdateDto) {
        if (Validator.isNull(ticketStatusUpdateDto.getRespondentData())) {
            return false;
        }
        String againDeliverRecordId = ticketStatusUpdateDto.getRespondentData().getAgainDeliverRecordId();
        if (Validator.isEmpty(againDeliverRecordId)) {
            if (log.isInfoEnabled()) {
                log.info("[ticket] 二次投放更改工单状态,没有传递二次投放记录id。ticketStatusUpdateDto={}", ticketStatusUpdateDto);
            }
            return false;
        }

        List<String> ruleIdList = ticketStatusUpdateDto.getRuleIdList();
        if (CollUtil.isEmpty(ruleIdList)) {
            if (log.isInfoEnabled()) {
                log.info("[ticket] 二次投放更改工单状态。ticketStatusUpdateDto={}", ticketStatusUpdateDto);
            }
            return false;
        }
        String ruleId = ticketStatusUpdateDto.getRuleIdList().get(0);
        if (Validator.isEmpty(ruleId)) {
            if (log.isInfoEnabled()) {
                log.info("[ticket] 二次投放更改工单状态。ruleId={}", ruleId);
            }
            return false;
        }
        if (log.isInfoEnabled()) {
            log.info("[ticket] 二次投放更改工单状态。参数校验通过");
        }
        return true;
    }

    @Override
    public ServiceResult<FileDTO> export(ExportTicketParam param) {
        PageDTO<Ticket> ticketPageDTO = ticketDao.pageTicket(param);
        List<Ticket> tickets = ticketPageDTO.getData();

        List<ExportTicketDTO> list = new ArrayList<>(tickets.size());
        ExportTicketDTO dto;
        List<TicketConfig> ticketConfigList = ticketConfigDao.listByOrgId(param.getOrgId());
        Map<String, String> idAndTitleMap = new HashMap<>();
        for (TicketConfig ticketConfig : ticketConfigList) {
            if (Objects.nonNull(ticketConfig)) {
                idAndTitleMap.put(ticketConfig.getId(), ticketConfig.getTitle());
            }
        }
        // 1、对ticket进行转换
        for (Ticket ticket : tickets) {
            dto = new ExportTicketDTO();
            // 2、编号
            dto.setTicketNo(ticket.getTicketNo());
            // 3、类型
            if (TicketTypeEnum.UN_NEED_DEAL.getIndex().equals(ticket.getType())) {
                dto.setTtype("预警");
            } else if (TicketTypeEnum.NEED_DEAL.getIndex().equals(ticket.getType())) {
                dto.setTtype("工单");
            } else {
                dto.setTtype("");
            }
            // 4、状态
            if (Objects.nonNull(ticket.getStatus())) {
                TicketExportStatusEnum statusEnum = TicketExportStatusEnum.getByIndex(ticket.getStatus());
                if (Objects.nonNull(statusEnum)) {
                    dto.setStatus(statusEnum.getName());
                } else {
                    dto.setStatus("");
                }
            } else {
                dto.setStatus("");
            }
            // 系统创建的工单所属项目、逾期情况为"-",生成规则为工单名
            if (Integer.valueOf(2).equals(ticket.getOperationType())) {
                // 5、生成规则
                dto.setRuleTitle(ticket.getName());
                // 6、所属项目
                dto.setSurveyTitle("-");
                // 7、所属组织
                dto.setRegionName(ticket.getRegionName());
                // 8、逾期情况
                dto.setOverDue("-");
            } else {
                // 5、生成规则
                dto.setRuleTitle(idAndTitleMap.getOrDefault(ticket.getTicketConfigId(), ""));
                // 6、所属项目
                dto.setSurveyTitle(ticket.getSurveyTitle());
                // 7、所属组织
                dto.setRegionName(ticket.getRegionName());
                // 8、逾期情况
                if (Boolean.TRUE.equals(ticket.getHasAuditOverdue())) {
                    dto.setOverDue("审核逾期");
                } else if (Boolean.TRUE.equals(ticket.getHasFollowOverdue())) {
                    dto.setOverDue("处理逾期");
                } else {
                    dto.setOverDue("无逾期");
                }
            }

            // 9、创建人
            String createdId = ticket.getCreatedId();
            if (StrUtil.isNotEmpty(createdId)) {
                UserDTO user = ticketBaseUserGrpcService.getUser(createdId);
                dto.setCreatedName(user.getName());
            } else {
                // 若没有创建人 则默认为 系统
                dto.setCreatedName("系统");
            }
            // 10、当前处理人
            // 劝你不要审查这段代码,能跑起来就不错了
            TicketInfo ticketInfo = TicketInfoConvert.entityToInfo(ticket);
            List<UserDTO> userDTOS = getCurrentTicketUsers(ticketInfo);
            List<UserDTO> userDTOList = Optional.ofNullable(userDTOS).orElse(new ArrayList<>());
            if (CollUtil.isEmpty(userDTOList)) {
                dto.setHandlerName("系统");
            } else {
                StringJoiner stringJoiner = new StringJoiner(",");
                for (UserDTO user : userDTOList) {
                    if (Objects.nonNull(user)) {
                        stringJoiner.add(user.getName());
                    }
                }
                dto.setHandlerName(stringJoiner.toString());
            }

            // 11、生成时间
            dto.setCreatedAt(DateUtil.convertDteToString(ticket.getCreatedDate(), "yyyy-MM-dd HH:mm:ss"));
            list.add(dto);

        }
        // 上传至OSS
        String fileName = ExcelConstant.EXPORT_TICKET_FILE_PREFIX + cn.hutool.core.date.DateUtil.format(
                LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN) + ExcelExportUtil.XLSX_SUFFIX;
        File file = ExcelExportUtil.createExcelStream(list, ExportTicketDTO.class, fileName);
        //String url = ossService.uploadTempFile(file, fileName, OssPathEnum.TEMP_TICKET, 360, null, ExcelConstant.EXPORT_ACCOUNT_FILE_REMARK);
        String url = storageService.upload(file, fileName, OssPathEnum.TEMP_TICKET.getPath(), new Dict()).getUrl();
        if (Objects.nonNull(file)) {
            file.delete();
        }
        // 返回上传文件数据
        if (StrUtil.isEmpty(url)) {
            return ServiceResult.failWithCode(ReqErrorCodeEnum.PARAM_FORMAT, "上传失败");
        }
        return ServiceResult.success(new FileDTO(fileName, url));
    }

    /**
     * 根据参数获取工单图表数据
     *
     * @param regionFilter   工单图表统计 公司/区域 统计参数
     * @param datetimeFilter 工单图表统计 时间 统计参数
     * @return 工单图表数据
     * @author XiaoYunSong <ys.xiao@idiaoyan.com>
     * @date 2021/8/27 16:12
     */
    @Override
    public ServiceResult<DashboardDTO> getDashboardData(RegionFilter regionFilter, DatetimeFilter datetimeFilter) {
        DashboardDTO dashboardDTO = new DashboardDTO();
        dashboardDTO.setFollowOverdueCount(ticketDao.aggregateTicketFollowOverdueCount(regionFilter, datetimeFilter));
        // 因为审核这个机制已经没有了, 所以这个字段永远是0, 下面这行代码留着以此告示
        // dashboardDTO.setAuditOverdueCount(ticketDao.aggregateTicketAuditOverdueCount(regionFilter, datetimeFilter));
        dashboardDTO.setAuditOverdueCount(0L);
        dashboardDTO.setNewTicketTrends(getNewTicketTrends(regionFilter));
        log.error("[Ticket] 获取图表数据region:{},date:{}", regionFilter, datetimeFilter);
        List<StatusCountDTO> statusCountList = ticketDao.aggregateStatusCount(regionFilter, datetimeFilter);
        for (StatusCountDTO count : statusCountList) {
            TicketStatusEnum status = TicketStatusEnum.getByIndex(count.getStatus());
            if (Objects.isNull(status)) {
                log.error("[Ticket] 获取图表数据状态异常; StatusCountDTO: {}", count);
                continue;
            }

            dashboardDTO.setTotalCount(dashboardDTO.getTotalCount() + count.getCount());

            if (TicketStatusEnum.FOLLOWING.equals(status)) {
                dashboardDTO.setFollowingCount(count.getCount());
            } else if (TicketStatusEnum.AUDITING.equals(status)) {
                dashboardDTO.setAuditingCount(count.getCount());
            } else if (TicketStatusEnum.MANUAL_CLOSED.equals(status)
                    || TicketStatusEnum.SYSTEM_CLOSED.equals(status)
                    || TicketStatusEnum.CLOSED.equals(status)) {
                dashboardDTO.setClosedCount(count.getCount());
            } else if (TicketStatusEnum.FINISH.equals(status)) {
                dashboardDTO.setFinishCount(count.getCount());
            } else if (TicketStatusEnum.FAILED.equals(status)) {
                dashboardDTO.setFailCount(count.getCount());
            } else if (TicketStatusEnum.UNNEED_DEAL.equals(status)) {
                dashboardDTO.setUnNeedDeal(count.getCount());
                // 去除预警数量
                dashboardDTO.setTotalCount(dashboardDTO.getTotalCount() - count.getCount());
            }
        }

        return ServiceResult.success(dashboardDTO);
    }

    /**
     * 获取最近七天增加的工单数量统计
     *
     * @param regionFilter 工单图表统计 公司/区域 统计参数
     * @return 统计信息
     * @author XiaoYunSong <ys.xiao@idiaoyan.com>
     * @date 2021/8/27 16:03
     */
    private DashboardDTO.NewTicketTrends getNewTicketTrends(RegionFilter regionFilter) {
        int week = 7;
        Set<String> dateRange = new LinkedHashSet<>(week);
        Map<String, Integer> needDate = new LinkedHashMap<>(week), unNeedData = new LinkedHashMap<>(week);
        LocalDate localDate = LocalDate.now().minusDays(6);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for (int i = 0; i < week; i++) {
            String dateStr = formatter.format(localDate.plusDays(i));
            dateRange.add(dateStr);
            needDate.put(dateStr, 0);
            unNeedData.put(dateStr, 0);
        }

        LocalDateTime startTime = LocalDateTime.of(LocalDate.now().minusDays(6), LocalTime.MIN);
        LocalDateTime endTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
        List<DateTypeCountDTO> dateTypeCountDtoList = ticketDao.aggregateTicketCount(regionFilter, startTime, endTime);
        for (DateTypeCountDTO dateTypeCountDTO : dateTypeCountDtoList) {
            if (!dateRange.contains(dateTypeCountDTO.getDate())) {
                continue;
            }
            if (TicketTypeEnum.UN_NEED_DEAL.getIndex().equals(dateTypeCountDTO.getType())) {
                unNeedData.put(dateTypeCountDTO.getDate(), dateTypeCountDTO.getCount());
            } else {
                needDate.put(dateTypeCountDTO.getDate(), dateTypeCountDTO.getCount());
            }
        }
        DashboardDTO.NewTicketTrends newTicketTrends = new DashboardDTO.NewTicketTrends();
        newTicketTrends.setDateRange(dateRange);
        newTicketTrends.setAlertCount(new ArrayList<>(unNeedData.values()));
        newTicketTrends.setTicketCount(new ArrayList<>(needDate.values()));
        return newTicketTrends;
    }

    @Override
    public ServiceResult<PageDTO<TicketPageVO>> pageTickets(ExportTicketParam param) {
        PageDTO<Ticket> ticketPageDTO = ticketDao.pageTicket(param);
        PageDTO<TicketPageVO> pageDTO = new PageDTO<>();
        pageDTO.setTotal(ticketPageDTO.getTotal());
        pageDTO.setPage(param.getPage());

        List<Ticket> ticketList = ticketPageDTO.getData();
        List<TicketPageVO> ticketPageVOS = new ArrayList<>();
        if (CollectionUtils.isEmpty(ticketList)) {
            pageDTO.setData(ticketPageVOS);
            return ServiceResult.success(pageDTO);
        }

        Set<String> ticketConfigIds = new HashSet<>();
        Set<String> userIds = new HashSet<>();
        Set<String> surveyIds = new HashSet<>();
        for (Ticket ticket : ticketList) {
            if (!ObjectUtils.isEmpty(ticket.getTicketConfigId())) {
                ticketConfigIds.add(ticket.getTicketConfigId());
            }
            if (!ObjectUtils.isEmpty(ticket.getCreatedId())) {
                userIds.add(ticket.getCreatedId());
            }
            if (!ObjectUtils.isEmpty(ticket.getSurveyId())) {
                surveyIds.add(ticket.getSurveyId());
            }
        }
        // 查出这一批工单对应的工单配置 做缓存
        Map<String, TicketPageVO.AlertRule> alertCache = new HashMap<>();
        List<TicketConfig> ticketConfigs = ticketConfigDao.listByIds(ticketConfigIds);
        if (!CollectionUtils.isEmpty(ticketConfigs)) {
            for (TicketConfig ticketConfig : ticketConfigs) {
                alertCache.put(ticketConfig.getId(),
                        new TicketPageVO.AlertRule(ticketConfig.getTitle(), ticketConfig.getId()));
            }
        }
        // 查出这一批工单对应的创建人 做缓存
        Map<String, String> usernameCache = new HashMap<>();
        List<UserDTO> userDTOs = ticketBaseUserGrpcService.listUserByIds(userIds);
        if (!CollectionUtils.isEmpty(userDTOs)) {
            for (UserDTO userDTO : userDTOs) {
                usernameCache.put(userDTO.getId(), userDTO.getName());
            }
        }
        // 查出这一批工单对应的问卷 做缓存
        Map<String, String> surveyTitleCache = new HashMap<>();
        List<QdesDTO> qdesDTOS = ticketBaseQdesGrpcService.listQdesProjectByIds(surveyIds);
        if (!CollectionUtils.isEmpty(qdesDTOS)) {
            for (QdesDTO qdesDTO : qdesDTOS) {
                surveyTitleCache.put(qdesDTO.getId(), qdesDTO.getTitle());
            }
        }

        for (Ticket ticket : ticketList) {

            TicketPageVO ticketPageVO = TicketVOConvert.entityToTicketPageVO(ticket);
            ticketPageVO.setTicketDeadline(null);

            // 创建人; 若没有创建人 则默认为 系统
            String username = usernameCache.get(ticket.getCreatedId());
            if (ObjectUtils.isEmpty(username)) {
                username = "系统";
            }
            ticketPageVO.setCreatedName(username);

            // 问卷项目的最新标题; 若没有标题 则使用工单创建时存的问卷项目标题
            String surveyTitle = surveyTitleCache.get(ticket.getSurveyId());
            if (Objects.isNull(surveyTitle)) {
                surveyTitle = ticketPageVO.getSurveyTitle();
            }
            ticketPageVO.setSurveyTitle(surveyTitle);

            // 当前处理人 ,2.3修改为如果跟进设置跟进人为层级类型，则显示角色下所有用户
            TicketInfo ticketInfo = TicketInfoConvert.entityToInfo(ticket);
            List<UserDTO> userDTOList = getCurrentTicketUsers(ticketInfo);
            // 设置当前处理人
            if (CollectionUtils.isEmpty(userDTOList)) {
                ticketPageVO.setHandlerName(Collections.singletonList("系统"));
            } else {
                List<String> collect = userDTOList.stream().map(UserDTO::getName).collect(Collectors.toList());
                ticketPageVO.setHandlerName(collect);
            }

            // 新增工单名称 2.3版本添加
            ticketPageVO.setTicketName(ticket.getName());
            // 设置跟进逾期时间
            if (TicketStatusEnum.FOLLOWING.getIndex().equals(ticket.getStatus())) {
                ticketPageVO.setTicketDeadline(ticket.getFollowDeadline());
            }

            // 工单配置信息
            TicketPageVO.AlertRule alertRule = alertCache.get(ticket.getTicketConfigId());
            if (Objects.nonNull(alertRule)) {
                ticketPageVO.setAlertRuleList(Collections.singletonList(alertRule));
            }

            ticketPageVOS.add(ticketPageVO);
        }

        pageDTO.setData(ticketPageVOS);
        return ServiceResult.success(pageDTO);
    }

    /**
     * 删除工单
     *
     * @param ticketId 工单id
     * @return 删除的数量
     * @author XiaoYunSong <ys.xiao@idiaoyan.com>
     * @date 2021/8/26 15:09
     */
    @Override
    public ServiceResult<Long> deleteTicket(String ticketId) {
        Ticket ticket = ticketDao.findById(ticketId);
        if (Objects.isNull(ticket)) {
            return ServiceResult.failWithCode(ResourceErrorCodeEnum.NOT_FOUND, "工单不存在");
        }
        boolean successFlg = ticketDao.updateStatus(ticketId, TicketStatusEnum.DELETED);
        if (!successFlg) {
            return ServiceResult.failWithCode(ResourceErrorCodeEnum.WRITE_FAIL, "工单删除失败");
        }
        // 工单状态改变，给联系人足迹更新工单状态
        ticket = ticketDao.findById(ticketId);
        if (Objects.isNull(ticket)) {
            return ServiceResult.failWithCode(ResourceErrorCodeEnum.NOT_FOUND, "工单异常");
        }
        // 发送工单改变mq。ticket删除视为一种修改，直接发changed消息，其中status为删除
        ticketMessageSendService.publishTicketChangeMq(ticket);
        if (!ObjectUtils.isEmpty(ticket.getMemberId())) {
            // 工单的状态改变mq发布联系人足迹
            ticketMessageSendService.publishMemberFootSaveMqRefactor(ticket);
        }

        return ServiceResult.success(1L);
    }

    @Override
    public List<UserDTO> getCurrentTicketUsers(TicketInfo ticketInfo) {
        if (TicketStatusEnum.FOLLOWING.getIndex().equals(ticketInfo.getStatus())) {
            // 获取当前跟进工单的人.
            List<TicketFlow.FollowConfig> followConfig = ticketInfo.getTicketFlow().getFollowConfig();
            int currentFollowNode = ticketInfo.getCurrentFollowNode() - 1;
            TicketFollower instance = TicketFollower.getInstance(followConfig.get(currentFollowNode));
            if (Objects.isNull(instance)) {
                return Collections.emptyList();
            }
            Ticket ticket = new Ticket();
            ticket.setRegion(ticketInfo.getRegion());
            return ticketTask.getHasPermUserByFollower(ticket, instance);

        } else if (TicketStatusEnum.CLOSED.getIndex().equals(ticketInfo.getStatus())) {
            List<TicketRecord> records = ticketInfo.getTicketRecords();
            // 获取最近一条的记录
            // 有处理人时返回最近一条记录处理人
            if (CollectionUtil.isNotEmpty(records)) {
                List<TicketRecord> reverse = records.stream().sorted(Comparator.comparing(BaseEntity::getCreatedDate).reversed()).collect(Collectors.toList());
                TicketRecord lastCreated = reverse.get(0);
                UserDTO userDTO = new UserDTO();
                userDTO.setName(lastCreated.getUserName());
                userDTO.setId(lastCreated.getUserId());
                ticketInfo.setTicketRecords(reverse);
                return Collections.singletonList(userDTO);
            } else {
                TicketRecord lastOneByTicketId = ticketRecordDao.findLastOneByTicketId(ticketInfo.getId());
                UserDTO userDTO = new UserDTO();
                userDTO.setName(lastOneByTicketId.getUserName());
                userDTO.setId(lastOneByTicketId.getUserId());
                return Collections.singletonList(userDTO);
            }

        }
        return new ArrayList<>(2);
    }

    @Override
    public void createFootPrint(MemberFootPrint memberFootPrint) {
        Integer seq = memberFootPrint.getSeq();
        if (Validator.isNull(seq)) {
            log.error("[ticket] 没有传递答卷编号");
        }
        if (log.isInfoEnabled()) {
            log.info("[ticket] 同步联系人，工单刷到联系人足迹点：seq:{}, start", seq);
        }
        List<Ticket> ticketList = ticketDao.findBySeq(seq);
        for (Ticket ticket : ticketList) {
            // 调用rpc方法查询memberId
            String memberId = ticketSurveyGrpcService.getMemberIdBySeq(seq);
            if (log.isInfoEnabled()) {
                log.info("[ticket] 同步联系人，memberid={}", memberId);
            }
            // 工单表同步加上member_id
            ticketDao.updateTicketMemberId(ticket.getId(), memberId);
            // 工单同步到足迹点
            ticket.setMemberId(memberId);
            ticketMessageSendService.publishMemberFootSaveMqRefactor(ticket);
            if (log.isInfoEnabled()) {
                log.info("[ticket] 工单={}同步到足迹点", ticket.getId());
            }
        }
        if (log.isInfoEnabled()) {
            log.info("[ticket] 同步联系人，工单刷到联系人足迹点：seq:{}, end", seq);
        }
    }
}